/*
**	Command & Conquer Generals(tm)
**	Copyright 2025 Electronic Arts Inc.
**
**	This program is free software: you can redistribute it and/or modify
**	it under the terms of the GNU General Public License as published by
**	the Free Software Foundation, either version 3 of the License, or
**	(at your option) any later version.
**
**	This program is distributed in the hope that it will be useful,
**	but WITHOUT ANY WARRANTY; without even the implied warranty of
**	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
**	GNU General Public License for more details.
**
**	You should have received a copy of the GNU General Public License
**	along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

//
// This module takes care of all the Chat API stuff
//

#include <list>
#include "GameSpy/ghttp/ghttp.h"
#include "downloadManager.h"

#include "chatapi.h"

//#include "api/wolapi_i.c"  // This should only be in one .cpp file
#include <objbase.h>
#include <windows.h>
#include <initguid.h>
#include <olectl.h>
#include <mapicode.h>
#include "resource.h"
#include "winblows.h"
#include <crtdbg.h>
#include "process.h"
#include "WWDownload/registry.h"
#include "WWDownload/urlBuilder.h"
#include "debug.h"

enum EVENT_TYPES
{
  NOUPDATE_EVENT=0,    // don't need to update
  ABORT_EVENT,
  NUM_EVENTS       // keep last
};

HANDLE       Events[NUM_EVENTS];

char         g_UpdateString[256];   // for the filename
char         g_DLTimeRem[80];
char         g_DLBytesLeft[80];
char         g_DLBPS[80];

int          g_Finished=0;


HWND         g_DownloadWindow;
HWND         g_ContactWindow;
HWND         g_PrimaryWindow;

static bool checkingForPatch = false;
static int checksLeft = 0;
static bool cantConnect = false;
static std::list<QueuedDownload> queuedDownloads;

BOOL CALLBACK downloadDialogProc( HWND hwndDlg, UINT uMsg, WPARAM wParam,
    LPARAM lParam );

///////////////////////////////////////////////////////////////////////////////////////

static void startOnline( void );

///////////////////////////////////////////////////////////////////////////////////////

QueuedDownload TheDownload;

class DownloadManagerMunkee : public DownloadManager
{
public:
	DownloadManagerMunkee() { }
	virtual HRESULT OnError( int error );
	virtual HRESULT OnEnd();
	virtual HRESULT OnProgressUpdate( int bytesread, int totalsize, int timetaken, int timeleft );
	virtual HRESULT OnStatusUpdate( int status );
	virtual HRESULT downloadFile( std::string server, std::string username, std::string password, std::string file, std::string localfile, std::string regkey, bool tryResume );
};

///////////////////////////////////////////////////////////////////////////////////////

HRESULT DownloadManagerMunkee::downloadFile( std::string server, std::string username, std::string password, std::string file, std::string localfile, std::string regkey, bool tryResume )
{
	/*
	if (staticTextFile)
	{
		UnicodeString fileString;
		fileString.translate(file);
		GadgetStaticTextSetText(staticTextFile, fileString);
	}
	*/
	return DownloadManager::downloadFile( server, username, password, file, localfile, regkey, tryResume );
}
HRESULT DownloadManagerMunkee::OnError( int error )
{
	HRESULT ret = DownloadManager::OnError( error );
  g_Finished = -1;
	return ret;
}
HRESULT DownloadManagerMunkee::OnEnd()
{
	HRESULT ret = DownloadManager::OnEnd();
  g_Finished = 1;
	return ret;
}
HRESULT DownloadManagerMunkee::OnProgressUpdate( int bytesread, int totalsize, int timetaken, int timeleft )
{
	HRESULT ret = DownloadManager::OnProgressUpdate( bytesread, totalsize, timetaken, timeleft );

  SendDlgItemMessage( g_DownloadWindow, IDC_PROGRESS, PBM_SETPOS, (WPARAM)(bytesread * 100) / totalsize, 0 );
  char temp[256];

  if( timeleft > 0 )
  {
    //DBGMSG("Bytes read: "<<bytesread<<".    Time left: "<<timeleft<<" seconds");
 	 LoadString(Global_instance, TXT_TIME_REMAIN, temp, sizeof(temp));
    sprintf(g_DLTimeRem,temp,(timeleft/60),(timeleft%60));

    LoadString(Global_instance, TXT_BPS, temp, sizeof(temp));
    sprintf(g_DLBPS,temp,bytesread/timetaken);
  }
  LoadString(Global_instance, TXT_BYTES_READ, temp, sizeof(temp));
  sprintf(g_DLBytesLeft,temp,bytesread,totalsize);
	return ret;
}
HRESULT DownloadManagerMunkee::OnStatusUpdate( int status )
{
	HRESULT ret = DownloadManager::OnStatusUpdate( status );
	SetWindowText(g_DownloadWindow, getStatusString().c_str());
	return ret;
}

///////////////////////////////////////////////////////////////////////////////////////
BOOL CALLBACK simpleDialogProc( HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam )
{
  switch( uMsg )
  {
    case WM_INITDIALOG:
      return(TRUE);
    break;
    case WM_CLOSE:
      DestroyWindow(hwnd);
      PostQuitMessage(0);
      exit(0);
    break;
  }
  return(FALSE);
}

static void startOnline( void )
{
	checkingForPatch = false;

  // Close that contacting window
  DestroyWindow(g_ContactWindow);
  g_ContactWindow=NULL;

	if (cantConnect)
	{
		MessageBox(NULL, "Can't Connect", "Command and Conquer Generals", MB_OK);
		exit(0);
	}
	else if (queuedDownloads.size())
	{
		if (MessageBox(NULL, "Patches Available.  Download?", "Command and Conquer Generals", MB_YESNO) == IDYES)
		{
			DEBUG_LOG(("Downloading patches\n"));
			while (queuedDownloads.size())
			{
				TheDownload = queuedDownloads.front();
				queuedDownloads.pop_front();
				TheDownloadManager = new DownloadManagerMunkee;
				/**/
		    int retVal = DialogBox(Global_instance, MAKEINTRESOURCE(IDD_DOWNLOAD_DIALOG), g_PrimaryWindow, downloadDialogProc);
				if (retVal)
				{
					DEBUG_LOG(("Error %d\n", GetLastError()));
				}
				/**/
				/*
				{
					//char *res = MAKEINTRESOURCE(IDD_CONNECTING);
					char *res = MAKEINTRESOURCE(IDD_DOWNLOAD_DIALOG1);
					g_DownloadWindow=CreateDialog(Global_instance,res,g_PrimaryWindow,simpleDialogProc);
					ShowWindow(g_DownloadWindow,SW_SHOWNORMAL);
					SetForegroundWindow(g_DownloadWindow);
				}
				*/
				delete TheDownloadManager;
				TheDownloadManager = NULL;

				if (g_Finished != 1)
				{
					// Download failed
					//DBGMSG("Download failed: "<<retval);
					SetEvent(Events[ABORT_EVENT]);
					return;
				}
			}
			exit(0);
		}
		else
		{
			exit(0);
		}
	}
	else
	{
		MessageBox(NULL, "No Patches Available", "Command and Conquer Generals", MB_OK);
		exit(0);
	}
}

///////////////////////////////////////////////////////////////////////////////////////

static std::string trim(std::string s, const std::string& delim)
{
	unsigned int i;
	i = s.find_first_not_of(delim);
	if (i != s.npos)
	{
		s = s.substr(i);
	}

	i = s.find_last_not_of(delim);
	if (i>=0 && i<s.npos)
	{
		s = s.substr(0, i+1);
	}

	return s;
}

///////////////////////////////////////////////////////////////////////////////////////

static std::string getNextLine(std::string in, std::string& remainder)
{
	int lineEnd;
	lineEnd = in.find_first_of("\n\r", 0);
	if (lineEnd < 1)
	{
		remainder = "";
		return in;
	}

	std::string out = in.substr(0, lineEnd);
	remainder = in.substr(lineEnd+1);

	remainder = trim(remainder, "\r\n\t ");
	out = trim(out, "\r\t\n ");
}

///////////////////////////////////////////////////////////////////////////////////////

//-----------------------------------------------------------------------------
inline const char* skipSeps(const char* p, const char* seps)
{
	while (*p && strchr(seps, *p) != NULL)
		++p;
	return p;
}

//-----------------------------------------------------------------------------
inline const char* skipNonSeps(const char* p, const char* seps)
{
	while (*p && strchr(seps, *p) == NULL)
		++p;
	return p;
}

//-----------------------------------------------------------------------------
bool nextToken(std::string& base, std::string& tok, const char* seps = NULL)
{
	if (base.empty())
		return false;

	if (seps == NULL)
		seps = " \n\r\t";

	const char* start = skipSeps(base.c_str(), seps);
	const char* end = skipNonSeps(start, seps);

	if (end > start)
	{
		int len = end - start;
		char* tmp = new char[len+1];
		memcpy(tmp, start, len);
		tmp[len] = 0;

		tok = tmp;
		delete[] tmp;

		base = end;

		return true;
	}
	else
	{
		base = tok = "";
		return false;
	}
}

///////////////////////////////////////////////////////////////////////////////////////

static void queuePatch(bool mandatory, std::string downloadURL)
{
	// downloadURL is of the form "ftp://ftp.ea.com:user@pass/pub/munkee/bananna.rtp"
	QueuedDownload q;
	bool success = true;

	std::string connectionType;
	success = success && nextToken(downloadURL, connectionType, ":");

	std::string server;
	success = success && nextToken(downloadURL, server, ":/");

	std::string user;
	success = success && nextToken(downloadURL, user, ":@");

	std::string pass;
	success = success && nextToken(downloadURL, pass, "@/");

	std::string filePath;
	success = success && nextToken(downloadURL, filePath, "");

	if (!success && !user.empty())
	{
		// no user/pass combo - move the file into it's proper place
		filePath = user;
		user = ""; // LFeenanEA - Credentials removed as per Security requirements
		pass = "";
		success = true;
	}

	std::string fileStr = filePath;
	unsigned int slashPos = filePath.find_last_of('/');
	std::string fileDir = "patches\\";
	std::string fileName = "";
	if (slashPos == filePath.npos)
	{
		fileName = filePath;
	}
	else
	{
		fileName = filePath.substr(slashPos+1);
	}
	fileDir.append(fileName);

	DEBUG_LOG(("download URL split: %d [%s] [%s] [%s] [%s] [%s] [%s] [%s]\n",
		success, connectionType.c_str(), server.c_str(), user.c_str(), pass.c_str(),
		filePath.c_str(), fileName.c_str(), fileDir.c_str()));

	if (!success)
		return;

	q.file = filePath;
	q.localFile = fileDir;
	q.password = pass;
	q.regKey = "";
	q.server = server;
	q.tryResume = true;
	q.userName = user;

	std::list<QueuedDownload>::iterator it = queuedDownloads.begin();
	while (it != queuedDownloads.end())
	{
		if (it->localFile == q.localFile)
			return; // don't add it if it exists already (because we can check multiple times)
		++it;
	}

	queuedDownloads.push_back(q);
}

///////////////////////////////////////////////////////////////////////////////////////

static GHTTPBool patchCheckCallback( GHTTPRequest request, GHTTPResult result, char * buffer, int bufferLen, void * param )
{
	--checksLeft;
	DEBUG_ASSERTCRASH(checksLeft>=0, ("Too many callbacks"));

	DEBUG_LOG(("Result=%d, buffer=[%s], len=%d\n", result, buffer, bufferLen));
	if (result != GHTTPSuccess)
	{
		cantConnect = true;
		if (!checksLeft)
		{
			startOnline();
		}
		return GHTTPTrue;
	}

	std::string message = buffer;
	std::string line;
	while (nextToken(message, line, "\r\n"))
	{
		std::string type, req, url;
		bool ok = true;
		ok = ok && nextToken(line, type, " ");
		ok = ok && nextToken(line, req, " ");
		ok = ok && nextToken(line, url, " ");
		if (ok && type == "patch")
		{
			DEBUG_LOG(("Saw a patch: %d/[%s]\n", atoi(req.c_str()), url.c_str()));
			queuePatch( atoi(req.c_str()), url );
		}
		else if (ok && type == "server")
		{
		}
	}

	if (!checksLeft)
	{
		startOnline();
	}

	return GHTTPTrue;
}

///////////////////////////////////////////////////////////////////////////////////////

static void StartPatchCheck( void )
{
	checkingForPatch = true;
	std::string gameURL, mapURL;
	std::string configURL, motdURL;

	FormatURLFromRegistry(gameURL, mapURL, configURL, motdURL);

	std::string proxy;
	if (GetStringFromRegistry("", "Proxy", proxy))
	{
		if (!proxy.empty())
		{
			ghttpSetProxy(proxy.c_str());
		}
	}

	// check for a patch first
	checksLeft = 2;
	cantConnect = false;
	ghttpGet(gameURL.c_str(), GHTTPFalse, patchCheckCallback, NULL);
	ghttpGet(mapURL.c_str(), GHTTPFalse, patchCheckCallback, NULL);

	DEBUG_LOG(("Started looking for patches at '%s' && '%s'\n", gameURL.c_str(), mapURL.c_str()));
}

///////////////////////////////////////////////////////////////////////////////////////


BOOL CALLBACK downloadDialogProc( HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam )
{
	//   HRESULT res;
	
	int cmd = LOWORD(wParam);
	switch( uMsg )
	{
		case WM_COMMAND:
			if ( cmd == IDC_DLABORT )
			{
				
				char abort[128];
				char abortdload[256];
				LoadString(Global_instance, TXT_ABORT_DOWNLOAD, abortdload, sizeof(abortdload));
				LoadString(Global_instance, TXT_ABORT, abort, sizeof(abort));
				
				if (MessageBox(g_PrimaryWindow,abortdload,abort,MB_YESNO)==IDYES)
				{
					TheDownloadManager->reset();
					
					EndDialog( hwndDlg, g_Finished );
					DestroyWindow(hwndDlg);
				}
			}
			else
			{
				return FALSE;
			}
			break;
			
		case WM_INITDIALOG:
			//SetupDownload();
			
			SendMessage(hwndDlg, WM_SETICON,(WPARAM)ICON_SMALL,
				(LPARAM)LoadIcon(Global_instance, MAKEINTRESOURCE(IDI_ICON1)));
			
			g_DLTimeRem[0]=0;
			g_DLBytesLeft[0]=0;
			g_DLBPS[0]=0;
			
			//SetDlgItemText( hwndDlg, IDC_DOWNLOADTITLE, g_UpdateString);
			//SetWindowText(hwndDlg, g_UpdateString);
			
			SetDlgItemText( hwndDlg, IDC_TIMEREM, g_DLTimeRem);
			SetDlgItemText( hwndDlg, IDC_BYTESLEFT, g_DLBytesLeft);
			// SetDlgItemText( hwndDlg, IDC_BPS, g_DLBPS );
			
			// Work out the full file name
			//char    fullpath[_MAX_PATH];
			//char    localfile[_MAX_PATH];
			//sprintf( fullpath, "%s/%s", g_Update->patchpath,g_Update->patchfile);
			//sprintf(localfile,"%s\\%s",g_Update->localpath,g_Update->patchfile);
			
			// Create the directory
			//CreateDirectory((char *)g_Update->localpath, NULL );
			
			TheDownloadManager->downloadFile(TheDownload.server, TheDownload.userName, TheDownload.password,
				TheDownload.file, TheDownload.localFile, TheDownload.regKey, TheDownload.tryResume);
				/*
				res=pDownload->DownloadFile((char *)g_Update->server, (char *)g_Update->login, (char *)g_Update->password,
				fullpath, localfile, APP_REG_KEY);
				
			*/
			g_DownloadWindow = hwndDlg;
			g_Finished = 0;
			SetTimer( hwndDlg, 1, 200, NULL );    // was 50
			
			break;
			
		case WM_TIMER:
			DEBUG_LOG(("TIMER\n"));
			if( g_Finished == 0 )
			{
				DEBUG_LOG(("Entering PumpMsgs\n"));
				TheDownloadManager->update();
				/*
				pDownload->PumpMessages();
				*/
				DEBUG_LOG(("Done with PumpMsgs\n"));
				if (strlen(g_DLTimeRem))
					SetDlgItemText( hwndDlg, IDC_TIMEREM, g_DLTimeRem );
				if (strlen(g_DLBytesLeft))
					SetDlgItemText( hwndDlg, IDC_BYTESLEFT, g_DLBytesLeft );
				//if (strlen(g_DLBPS))
				//  SetDlgItemText( hwndDlg, IDC_BPS, g_DLBPS );
			}
			else
			{
				DEBUG_LOG(("TIMER: Finished\n"));
				EndDialog( hwndDlg, g_Finished );
				DestroyWindow( hwndDlg );
			}
			break;
			
		case WM_DESTROY:
			KillTimer( hwndDlg, 1 );
			//ClosedownDownload();
			
			//DBGMSG("WM_DESTROY");
			break;
			
		case WM_SETFONT:
			return TRUE;
			
		default:
			return FALSE;
	}
	return TRUE;
}



DWORD        dwChatAdvise;
DWORD        dwDownloadAdvise;

//Update      *g_Update;

uint32       g_AppVer=-1;


BOOL CALLBACK Download_Dialog_Proc( HWND hwndDlg, UINT uMsg, WPARAM wParam,
    LPARAM lParam );

BOOL CALLBACK Simple_Dialog_Proc( HWND hwndDlg, UINT uMsg, WPARAM wParam,
    LPARAM lParam );

HWND CreatePrimaryWin(void);

char const * Fetch_String(int id);


//
// Create a primary window
//
HWND CreatePrimaryWin(void)
{
  HWND                hwnd;
  WNDCLASS            wc;
  char                name[256];

  sprintf(name,Fetch_String(TXT_TITLE));

  //DBGMSG("CreatePrimary: "<<name);

  /*
  ** set up and register window class
  */
  wc.style = CS_HREDRAW | CS_VREDRAW;
  wc.lpfnWndProc = DefWindowProc;
  wc.cbClsExtra = 0;            // Don't need any extra class data
  wc.cbWndExtra = 0;            // No extra win data
  wc.hInstance = Global_instance;
  wc.hIcon=LoadIcon(Global_instance, MAKEINTRESOURCE(IDI_ICON1));
  wc.hCursor = NULL;  /////////LoadCursor( NULL, IDC_ARROW );
  wc.hbrBackground = NULL;
  wc.lpszMenuName = name;
  wc.lpszClassName = name;
  RegisterClass( &wc );
    
  /*
  ** create a window
  */
  hwnd = CreateWindowEx(
      WS_EX_APPWINDOW,
      name,
      name,
      WS_POPUP,
      0, 0,

      //GetSystemMetrics( SM_CXSCREEN ),
      //GetSystemMetrics( SM_CYSCREEN ),
      0,0,

      NULL,
      NULL,
      Global_instance,
      NULL );

  SendMessage(hwnd,WM_SETICON,(WPARAM)ICON_SMALL,
      (LPARAM)LoadIcon(Global_instance, MAKEINTRESOURCE(IDI_ICON1)));

  ShowWindow(hwnd,SW_SHOWNORMAL);

  return(hwnd);
}



//
// Dispatch pending windows events
//
void DispatchEvents(void)
{
  MSG msg;
  int counter=0;
  while(PeekMessage(&msg,NULL,0,0, PM_REMOVE))
  {
    TranslateMessage(&msg);
    DispatchMessage(&msg);
	 counter++;
	 if (counter==256)  // just in case
	   break;
  }
}

//
// Check for patches
//
int main(int argc, char *argv[])
{
	InitCommonControls();

	/*
  g_PrimaryWindow=CreatePrimaryWin();  // Create the main window
  DispatchEvents();  // process some win messages 
	*/

/*
  // Check if they've registered before, if not ask them if they want to
  bool have_registered=false;
  if (RegOpenKeyEx(HKEY_LOCAL_MACHINE,REGISTER_REG_KEY,0,KEY_READ,&rKey)==ERROR_SUCCESS)
  {
    char username[64];
    valuesize=sizeof(username);
    if (RegQueryValueEx(rKey,"UserName",NULL,&type,(uint8 *)username,&valuesize)==ERROR_SUCCESS)
      have_registered=true;
    RegCloseKey(rKey);
  }
  if (!have_registered)
  {
    if (RegOpenKeyEx(HKEY_CLASSES_ROOT,NICK_REG_KEY,0,KEY_READ,&rKey)==ERROR_SUCCESS)
    {
      have_registered=true;
      RegCloseKey(rKey);
    }
  }

  if (!have_registered)
  {
    if (MessageBox(NULL,Fetch_String(TXT_REGNOW),Fetch_String(TXT_TITLE),MB_YESNO)==IDNO)
      have_registered=true;  // pretend they've alredy registered
  }

  if (!have_registered)
  {
    // figure out where the registration app is installed & launch it, continue
    //    after it exits.
    if (RegOpenKeyEx(HKEY_LOCAL_MACHINE,REGISTER_REG_APP,0,KEY_READ,&rKey)==ERROR_SUCCESS)
    {
      char regapp[300];
      valuesize=sizeof(regapp);
      if ((RegQueryValueEx(rKey,"InstallPath",NULL,&type,(uint8 *)regapp,&valuesize)==ERROR_SUCCESS)&&
             (strlen(regapp) > 8))
      {
        // Launch the process
        SHELLEXECUTEINFO info;
        memset(&info,0,sizeof(info));
        info.cbSize=sizeof(info);
        info.fMask=SEE_MASK_NOCLOSEPROCESS;
        info.hwnd=g_PrimaryWindow;
        info.lpVerb=NULL;
        info.lpFile=regapp;
        info.lpParameters=NULL;
        info.lpDirectory=".";
        info.nShow=SW_SHOW;
        ShellExecuteEx(&info);

        // Can't wait infinite or the other process will never create its window
        //   Only Bill himself knows why this is happening
        while(1)  // Wait for completion
        {
          DispatchEvents();
          if (WaitForSingleObject(info.hProcess,500)!=WAIT_TIMEOUT)
            break;
        }
      }
      RegCloseKey(rKey);
    }
  }
  // OK, done with that crap go on to the task at hand now....
*/


  // Find the game version
  g_AppVer = -1;
	if (!GetUnsignedIntFromRegistry("", "Version", g_AppVer))
	{
    MessageBox(g_PrimaryWindow,Fetch_String(TXT_INSTALL_PROBLEM),Fetch_String(TXT_ERROR),MB_OK);
    exit(0);
	}
  // OK, have the current game version now

  g_PrimaryWindow=CreatePrimaryWin();  // Create the main window
  DispatchEvents();  // process some win messages 

  // Popup the "contacting" window
  g_ContactWindow=CreateDialog(Global_instance,MAKEINTRESOURCE(IDD_CONNECTING),g_PrimaryWindow,Simple_Dialog_Proc);
  ShowWindow(g_ContactWindow,SW_SHOWNORMAL);
  SetForegroundWindow(g_ContactWindow);
  DispatchEvents();  // process some win messages


  // Setup the Westwood Online stuff
  Startup_Chat();

  Update_If_Required();

  Shutdown_Chat();

  return(0);
}


typedef struct SRecord {
	int ID;						// ID number of the string resource.
	int TimeStamp;				// 'Time' that this string was last requested.
	char String[2048];			// Copy of string resource.

	SRecord(void) : ID(-1), TimeStamp(-1) {}
} SRecord;


/***********************************************************************************************
 * Fetch_String -- Fetches a string resource.                                                  *
 *                                                                                             *
 *    Fetches a string resource and returns a pointer to its text.                             *
 *                                                                                             *
 * INPUT:   id -- The ID number of the string resource to fetch.                               *
 *                                                                                             *
 * OUTPUT:  Returns with a pointer to the actual text of the string resource.                  *
 *                                                                                             *
 * WARNINGS:   none                                                                            *
 *                                                                                             *
 * HISTORY:                                                                                    *
 *   12/25/1996 JLB : Created.                                                                 *
 *=============================================================================================*/
char const * Fetch_String(int id)
{
	static SRecord _buffers[64];
	static int _time = 0;

	/*
	**	Determine if the string ID requested is valid. If not then return an empty string pointer.
	*/
	if (id == -1 || id == TXT_NONE) return("");

	/*
	**	Adjust the 'time stamp' tracking value. This is an artificial value used merely to track
	**	the relative age of the strings requested.
	*/
	_time = _time+1;

	/*
	**	Check to see if the requested string has already been fetched into a buffer. If so, then
	**	return a pointer to that string (update the time stamp as well).
	*/
	for (int index = 0; index < ARRAY_SIZE(_buffers); index++) {
		if (_buffers[index].ID == id) {
			_buffers[index].TimeStamp = _time;
			return(_buffers[index].String);
		}
	}

	/*
	**	Find a suitable buffer to hold the string to be fetched. The buffer should either be
	**	empty or have the oldest fetched string.
	*/
	int oldest = -1;
	int oldtime = -1;
	for (int text = 0; text < ARRAY_SIZE(_buffers); text++) {
		if (oldest == -1 || oldtime > _buffers[text].TimeStamp) {
			oldest = text;
			oldtime = _buffers[text].TimeStamp;
			if (oldtime == -1 || _buffers[text].ID == -1) break;
		}
	}

	/*
	**	A suitable buffer has been found so fetch the string resource and then return a pointer
	**	to the string.
	*/
	char * stringptr = _buffers[oldest].String;
	_buffers[oldest].ID = id;
	_buffers[oldest].TimeStamp = _time;

   
	if (LoadString(Global_instance, id, stringptr, sizeof(_buffers[oldest].String)) == 0) {
		return("");
	}
   
   /******
   char resname[32];
   sprintf(resname,"#%d",id);
   HMODULE hmod=GetModuleHandle(NULL);
   HRSRC hrsrc=FindResourceEx(hmod, RT_STRING, MAKEINTRESOURCE(id), LANGID);
   if (hrsrc==0)
   {
     char message_buffer[256];
	  FormatMessage( FORMAT_MESSAGE_FROM_SYSTEM, NULL, GetLastError(), MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), &message_buffer[0], 256, NULL );
     
   }
   HGLOBAL resdata=LoadResource(NULL,hrsrc);
   LPVOID vdata=LockResource(resdata);
   strcpy(stringptr,(char *)vdata);
   *********/

	stringptr[sizeof(_buffers[oldest].String)-1] = '\0';
	return(stringptr);
}




void LogMsg(char *msg)
{
#ifdef _DEBUG
  FILE *out=fopen("register.log","a");
  fprintf(out,"%s\n",msg);
  fflush(out);
  fclose(out);
#endif
}




void Startup_Chat(void)
{
	/*
  //////CComObject<CChatEventSink>* g_pChatSink;
  HRESULT          hRes;
  g_pChatSink=NULL;

  CoCreateInstance(CLSID_Chat, NULL, CLSCTX_INPROC_SERVER,
      IID_IChat, (void**)&pChat);


  if (pChat==NULL)
  {
    char error[128];
    char apimissing[256];
  	 LoadString(Global_instance, TXT_API_MISSING, apimissing, sizeof(apimissing));
	 LoadString(Global_instance, TXT_ERROR, error, sizeof(error));
    MessageBox(g_PrimaryWindow,apimissing,error,MB_OK);
    exit(-5);
  }

  g_pChatSink=new CChatEventSink;

  // Get a connection point from the chat class
  IConnectionPoint           *pConnectionPoint=NULL;
  IConnectionPointContainer  *pContainer=NULL;

  dwChatAdvise=0;
  hRes=pChat->QueryInterface(IID_IConnectionPointContainer,(void**)&pContainer);
  _ASSERTE(SUCCEEDED(hRes));
  hRes=pContainer->FindConnectionPoint(IID_IChatEvent,&pConnectionPoint);
  _ASSERTE(SUCCEEDED(hRes));
  hRes=pConnectionPoint->Advise((IChatEvent *)g_pChatSink,&dwChatAdvise);
  _ASSERTE(SUCCEEDED(hRes));


  pChat->SetAttributeValue("RegPath",APP_REG_KEY);

  // ADD pConnectionPoint->Release();
	*/
}

void Shutdown_Chat(void)
{
	/*
    /////AtlUnadvise(pChat, IID_IChatEvent, dwChatAdvise);

    IConnectionPoint           *pConnectionPoint=NULL;
    IConnectionPointContainer  *pContainer=NULL;
    HRESULT                     hRes;

    hRes=pChat->QueryInterface(IID_IConnectionPointContainer,(void**)&pContainer);
    _ASSERTE(SUCCEEDED(hRes));
    hRes=pContainer->FindConnectionPoint(IID_IChatEvent,&pConnectionPoint);
    _ASSERTE(SUCCEEDED(hRes));
    pConnectionPoint->Unadvise(dwChatAdvise);

    pChat->Release();

    /////delete(g_pChatSink);   This appears to be bad....
    // ADD g_pChatSink->Release();
    // ADD pConnectionPoint->Release();
    // ADD pContainer->Release();
		*/
}



//
// Download a patch for the registration client if required
//   This uses the chat API for ver checking and FTP.
//
void Update_If_Required(void)
{
  int   retval;
  int   i;
  // Create the events
  for (i=0; i<NUM_EVENTS; i++)
    Events[i]=CreateEvent(NULL,FALSE,FALSE,NULL);

	StartPatchCheck();

	while (1)
	{
		ghttpThink();
    MSG msg;
    while(PeekMessage(&msg,NULL,0,0, PM_REMOVE))
    {
      TranslateMessage(&msg);
      DispatchMessage(&msg);
    }
    retval=WaitForMultipleObjectsEx(NUM_EVENTS,Events,FALSE,50,FALSE);
    if (retval==WAIT_TIMEOUT)
      continue;
    //DBGMSG("An event was set");
    retval-=WAIT_OBJECT_0;
    break;
  }

  //DBGMSG("Out of the loop")

  if (retval==ABORT_EVENT)
  {
     exit(0);
  }
  else
  {
    //DBGMSG("NO update required");
  }

  //DBGMSG("Shutting down");

  // close all the event objects
  for (i=0; i<NUM_EVENTS; i++)
    CloseHandle(Events[i]);

	/*
  Startup_Chat();
  int   retval;
  int   i;


  // Create the events
  for (i=0; i<NUM_EVENTS; i++)
    Events[i]=CreateEvent(NULL,FALSE,FALSE,NULL);

  /// For Testing....
  ///pChat->RequestServerList(1000,262364,"register","regpas98",15);
  ///pChat->RequestServerList(1000,300,"register","regpas98",15);

  pChat->RequestServerList(g_AppSku,g_AppVer,"register","regpas98",40);

  while(1)
  {
    pChat->PumpMessages();
    MSG msg;
    while(PeekMessage(&msg,NULL,0,0, PM_REMOVE))
    {
      TranslateMessage(&msg);
      DispatchMessage(&msg);
    }
    retval=WaitForMultipleObjectsEx(NUM_EVENTS,Events,FALSE,50,FALSE);
    if (retval==WAIT_TIMEOUT)
      continue;
    //DBGMSG("An event was set");
    retval-=WAIT_OBJECT_0;
    break;
  }

  //DBGMSG("Out of the loop")

  if (retval==ABORT_EVENT)
  {
     exit(0);
  }
  else
  {
    //DBGMSG("NO update required");
  }

  //DBGMSG("Shutting down");

  // close all the event objects
  for (i=0; i<NUM_EVENTS; i++)
    CloseHandle(Events[i]);

  Shutdown_Chat();
	*/
}






/*
CChatEventSink::CChatEventSink()
{
  m_cRef=0;  // init the refrence count
}


///////////////////////////////////////////////////////////
//
//			Interface IUnknown Methods
//
///////////////////////////////////////////////////////////
//
// QueryInterface
//
HRESULT __stdcall
CChatEventSink::QueryInterface(const IID& iid, void** ppv)
{
	if ((iid == IID_IUnknown) ||(iid == IID_IChatEvent))
	{
		*ppv = static_cast<IChatEvent*>(this) ;
	}
	else
	{
		*ppv = NULL;
		return E_NOINTERFACE;
	}
	(reinterpret_cast<IUnknown*>(*ppv))->AddRef() ;
	return S_OK ;
}

///////////////////////////////////////////////////////////
//
// AddRef
//
ULONG __stdcall
CChatEventSink::AddRef()
{
	return InterlockedIncrement(&m_cRef) ;
}

///////////////////////////////////////////////////////////
//
// Release
//
ULONG __stdcall
CChatEventSink::Release()
{
   if (InterlockedDecrement(&m_cRef) == 0)
	{
		delete this ;
      return 0 ;
	}
	return m_cRef;
}





///// DOWNLOAD

CDownloadEventSink::CDownloadEventSink()
{
  m_cRef=0;   // Ref counter
}

///////////////////////////////////////////////////////////
//
//			Interface IUnknown Methods
//
///////////////////////////////////////////////////////////
//
// QueryInterface
//
HRESULT __stdcall
CDownloadEventSink::QueryInterface(const IID& iid, void** ppv)
{
	if ((iid == IID_IUnknown) ||(iid == IID_IDownloadEvent))
	{
		*ppv = static_cast<IDownloadEvent*>(this) ;
	}
	else
	{
		*ppv = NULL;
		return E_NOINTERFACE;
	}
	(reinterpret_cast<IUnknown*>(*ppv))->AddRef() ;
	return S_OK ;
}

///////////////////////////////////////////////////////////
//
// AddRef
//
ULONG __stdcall
CDownloadEventSink::AddRef()
{
	return InterlockedIncrement(&m_cRef) ;
}

///////////////////////////////////////////////////////////
//
// Release
//
ULONG __stdcall
CDownloadEventSink::Release()
{
   if (InterlockedDecrement(&m_cRef) == 0)
	{
		delete this ;
      return 0 ;
	}
	return m_cRef;
}

*/






//// FTP Download stuff


void SetupDownload( void )
{
	/*
  HRESULT              hRes;

  g_pDownloadSink=NULL;

  CoCreateInstance(CLSID_Download, NULL, CLSCTX_INPROC_SERVER,
      IID_IDownload, (void**)&pDownload);
  _ASSERTE(pDownload);
  g_pDownloadSink=new CDownloadEventSink;

  // Get a connection point from the chat class
  IConnectionPoint           *pConnectionPoint=NULL;
  IConnectionPointContainer  *pContainer=NULL;
  dwDownloadAdvise = 0;

  hRes=pDownload->QueryInterface(IID_IConnectionPointContainer,(void**)&pContainer);
  _ASSERTE(SUCCEEDED(hRes));
  hRes=pContainer->FindConnectionPoint(IID_IDownloadEvent,&pConnectionPoint);
  _ASSERTE(SUCCEEDED(hRes));
  hRes=pConnectionPoint->Advise((IDownloadEvent *)g_pDownloadSink,&dwDownloadAdvise);
  _ASSERTE(SUCCEEDED(hRes));
	*/
}



void ClosedownDownload( void )
{
/*
  // AtlUnadvise(pDownload, IID_IDownloadEvent, dwDownloadAdvise);

  IConnectionPoint           *pConnectionPoint=NULL;
  IConnectionPointContainer  *pContainer=NULL;
  HRESULT                     hRes;

  hRes=pDownload->QueryInterface(IID_IConnectionPointContainer,(void**)&pContainer);
  _ASSERTE(SUCCEEDED(hRes));
  hRes=pContainer->FindConnectionPoint(IID_IDownloadEvent,&pConnectionPoint);
  _ASSERTE(SUCCEEDED(hRes));
  pConnectionPoint->Unadvise(dwDownloadAdvise);

	pDownload->Release();

   //////delete(g_pDownloadSink);  This appears to be bad....
*/
}



BOOL CALLBACK Download_Dialog_Proc( HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam )
{
//	char    fullpath[ 256 ];
//   char    localfile[ 256];
//   HRESULT res;

	switch( uMsg )
	{
		case WM_COMMAND:
			switch( LOWORD( wParam ) )
			{
				case IDC_DLABORT:
            {

               char abort[128];
               char abortdload[256];
  	            LoadString(Global_instance, TXT_ABORT_DOWNLOAD, abortdload, sizeof(abortdload));
               LoadString(Global_instance, TXT_ABORT, abort, sizeof(abort));

               if (MessageBox(g_PrimaryWindow,abortdload,abort,MB_YESNO)==IDYES)
               {
/*
					  pDownload->Abort();
*/
                 EndDialog( hwndDlg, g_Finished );
					  DestroyWindow(hwndDlg);
               }
            }
            break;

				default:
					return FALSE;
			}
			break;

		case WM_INITDIALOG:
			SetupDownload();

         SendMessage(hwndDlg, WM_SETICON,(WPARAM)ICON_SMALL,
           (LPARAM)LoadIcon(Global_instance, MAKEINTRESOURCE(IDI_ICON1)));

         g_DLTimeRem[0]=0;
         g_DLBytesLeft[0]=0;
         g_DLBPS[0]=0;

			//SetDlgItemText( hwndDlg, IDC_DOWNLOADTITLE, g_UpdateString);
         //SetWindowText(hwndDlg, g_UpdateString);

         SetDlgItemText( hwndDlg, IDC_TIMEREM, g_DLTimeRem);
         SetDlgItemText( hwndDlg, IDC_BYTESLEFT, g_DLBytesLeft);
         // SetDlgItemText( hwndDlg, IDC_BPS, g_DLBPS );

/*
         // Work out the full file name
         sprintf( fullpath, "%s/%s", g_Update->patchpath,g_Update->patchfile);
         sprintf(localfile,"%s\\%s",g_Update->localpath,g_Update->patchfile);

         // Create the directory
         CreateDirectory((char *)g_Update->localpath, NULL );

         res=pDownload->DownloadFile((char *)g_Update->server, (char *)g_Update->login, (char *)g_Update->password,
            fullpath, localfile, APP_REG_KEY);

*/
			g_DownloadWindow = hwndDlg;
			g_Finished = 0;
			SetTimer( hwndDlg, 1, 200, NULL );    // was 50

			break;

		case WM_TIMER:
      LogMsg("TIMER");
			if( g_Finished == 0 )
			{
            LogMsg("Entering PumpMsgs");
/*
				pDownload->PumpMessages();
*/
            LogMsg("Done with PumpMsgs");
            if (strlen(g_DLTimeRem))
              SetDlgItemText( hwndDlg, IDC_TIMEREM, g_DLTimeRem );
            if (strlen(g_DLBytesLeft))
              SetDlgItemText( hwndDlg, IDC_BYTESLEFT, g_DLBytesLeft );
            //if (strlen(g_DLBPS))
            //  SetDlgItemText( hwndDlg, IDC_BPS, g_DLBPS );
			}
			else
			{
            LogMsg("TIMER: Finished");
            EndDialog( hwndDlg, g_Finished );
				DestroyWindow( hwndDlg );
			}
			break;

		case WM_DESTROY:
			KillTimer( hwndDlg, 1 );
			ClosedownDownload();

         //DBGMSG("WM_DESTROY");
			break;

		case WM_SETFONT:
			return TRUE;

		default:
			return FALSE;
	}
	return TRUE;
}



// Whoeee this is an exciting one...
BOOL CALLBACK Simple_Dialog_Proc( HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam )
{
  switch( uMsg )
  {
    case WM_INITDIALOG:
      return(TRUE);
    break;
    case WM_CLOSE:
      DestroyWindow(hwnd);
      PostQuitMessage(0);
      exit(0);
    break;
  }
  return(FALSE);
}




/////////////////////////////////////////////////////////////////////////////
// CDownloadEventSink
//////////////////////////////////////////////////////////////////////////////

/*

STDMETHODIMP CDownloadEventSink::OnEnd(void)
{
  LogMsg("Finished!");
  g_Finished = 1;
  return(S_OK);
}

STDMETHODIMP CDownloadEventSink::OnError(int error)
{
  LogMsg("ERROR");
  g_Finished = -1;
  return(S_OK);
}


STDMETHODIMP CDownloadEventSink::OnProgressUpdate(int bytesread, int totalsize,
  int timetaken, int timeleft)
{
  SendDlgItemMessage( g_DownloadWindow, IDC_PROGRESS, PBM_SETPOS, (WPARAM)(bytesread * 100) / totalsize, 0 );
  char temp[256];

  if( timeleft > 0 )
  {
    //DBGMSG("Bytes read: "<<bytesread<<".    Time left: "<<timeleft<<" seconds");
 	 LoadString(Global_instance, TXT_TIME_REMAIN, temp, sizeof(temp));
    sprintf(g_DLTimeRem,temp,(timeleft/60),(timeleft%60));

    LoadString(Global_instance, TXT_BPS, temp, sizeof(temp));
    sprintf(g_DLBPS,temp,bytesread/timetaken);
  }
  LoadString(Global_instance, TXT_BYTES_READ, temp, sizeof(temp));
  sprintf(g_DLBytesLeft,temp,bytesread,totalsize);

  return(S_OK);
}


STDMETHODIMP CDownloadEventSink::OnStatusUpdate(int status)
{
  switch( status )
  {
    case DOWNLOADSTATUS_CONNECTING:
    {
	  //LogMsg( "Connecting..." );
     SetWindowText(g_DownloadWindow, Fetch_String(TXT_CONNECTING));
    }
    break;

    case DOWNLOADSTATUS_FINDINGFILE:
	  //LogMsg( "Finding patch..." );
     SetWindowText(g_DownloadWindow, g_UpdateString);
    break;

    case DOWNLOADSTATUS_DOWNLOADING:
	  //LogMsg( "Downloading patch..." );
     SetWindowText(g_DownloadWindow, g_UpdateString);
    break;

    default:
     //LogMsg("Unknown status update!");
    break;
  }
  return(S_OK);
}

//
// Just tell the FTP module to go ahead and resume
//
STDMETHODIMP CDownloadEventSink::OnQueryResume(void)
{
  return(DOWNLOADEVENT_RESUME);
}




/////////////////////////////////////////////////////////////////////////////
// CChatEventSink
//////////////////////////////////////////////////////////////////////////////


STDMETHODIMP CChatEventSink::OnServerList(HRESULT res, Server* servers)
{
  // If we get this, then we don't need to patch
  // Close that contacting window
  DestroyWindow(g_ContactWindow);
  g_ContactWindow=NULL;

  LogMsg("Server List");

  MessageBox(g_PrimaryWindow,Fetch_String(TXT_NO_PATCHES),Fetch_String(TXT_AUTO_UPDATE),MB_OK);

  SetEvent(Events[NOUPDATE_EVENT]);
  return(S_OK);
}

STDMETHODIMP CChatEventSink::OnServerBannedYou(HRESULT, time_t)
{
 return(S_OK);
}


STDMETHODIMP CChatEventSink::OnPageSend(HRESULT)
{
  return(S_OK);
}

STDMETHODIMP CChatEventSink::OnPaged(HRESULT, User *, LPCSTR)
{
  return(S_OK);
}


STDMETHODIMP CChatEventSink::OnFind(HRESULT, Channel *)
{
  return(S_OK);
}

STDMETHODIMP CChatEventSink::OnLogout(HRESULT, User *)
{
  return(S_OK);
}


STDMETHODIMP CChatEventSink::OnBusy(HRESULT)
{
  return(S_OK);
}

STDMETHODIMP CChatEventSink::OnIdle(HRESULT)
{
  return(S_OK);
}


STDMETHODIMP CChatEventSink::OnConnection(HRESULT, LPCSTR)
{
  return(S_OK);
}

STDMETHODIMP CChatEventSink::OnUserFlags(HRESULT,LPCSTR,unsigned int, unsigned int)
{
 return(S_OK);
}


STDMETHODIMP CChatEventSink::OnChannelCreate(HRESULT, Channel *)
{
  return(S_OK);
}

STDMETHODIMP CChatEventSink::OnChannelModify(HRESULT, Channel *)
{
  return(S_OK);
}


STDMETHODIMP CChatEventSink::OnChannelJoin(HRESULT, Channel *, User *)
{
  return(S_OK);
}


STDMETHODIMP CChatEventSink::OnChannelLeave(HRESULT, Channel *, User *)
{
  return(S_OK);
}


STDMETHODIMP CChatEventSink::OnChannelTopic(HRESULT, Channel *, LPCSTR)
{
  return(S_OK);
}

STDMETHODIMP CChatEventSink::OnChannelBan(HRESULT,  LPCSTR, int)
{
 return(S_OK);
}


STDMETHODIMP CChatEventSink::OnGroupList(HRESULT, Group *)
{
  return(S_OK);
}


STDMETHODIMP CChatEventSink::OnPublicMessage(HRESULT, Channel *, User *, LPCSTR)
{
  return(S_OK);
}


STDMETHODIMP CChatEventSink::OnPrivateMessage(HRESULT, User *,LPCSTR)
{
  return(S_OK);
}


STDMETHODIMP CChatEventSink::OnSystemMessage(HRESULT, LPCSTR)
{
  return(S_OK);
}

STDMETHODIMP CChatEventSink::OnUserLocale(long, struct User *)
{
   return(S_OK);
}

STDMETHODIMP CChatEventSink::OnUserTeam(long, struct User *)
{
   return(S_OK);
}

STDMETHODIMP CChatEventSink::OnSetLocale(long, enum Locale)
{
   return(S_OK);
}

STDMETHODIMP CChatEventSink::OnSetTeam(long, int)
{
   return(S_OK);
}

STDMETHODIMP CChatEventSink::OnNetStatus(HRESULT hr)
{
  char string[60];
  sprintf(string,"NetStatus %ld",hr);
  LogMsg(string);

  if ((hr==CHAT_E_CON_NETDOWN)||(hr==CHAT_E_CON_LOOKUP_FAILED)||
    (hr==CHAT_E_CON_ERROR)||(hr==CHAT_E_TIMEOUT))
  {
    char error[128];
    char cantconn[256];
  	 LoadString(Global_instance, TXT_CANT_CONTACT, cantconn, sizeof(cantconn));
	 LoadString(Global_instance, TXT_ERROR, error, sizeof(error));
    MessageBox(g_PrimaryWindow,cantconn,error,MB_OK);
    exit(-1);
  }

  return(S_OK);
}


STDMETHODIMP CChatEventSink::OnChannelList(HRESULT, Channel*)
{
  return(S_OK);
}


STDMETHODIMP CChatEventSink::OnUserList(HRESULT, Channel*, User*)
{
  return(S_OK);
}



//
// We got a list of updates to apply
//
STDMETHODIMP CChatEventSink::OnUpdateList(HRESULT r, Update * updates)
{
  Update     *tmp;
  int         numupdates=0;
  int         i=0;
  int         retval;

  static int  alreadyGotOne=0;

  LogMsg("Update list");

  // Close that contacting window
  DestroyWindow(g_ContactWindow);
  g_ContactWindow=NULL;

  if( FAILED(r) )
  {
    LogMsg("Failed");
    SetEvent(Events[ABORT_EVENT]);   // An error occurred, bail out
  }

  if( updates == NULL )  // shouldn't happen
	return S_OK;


  if (alreadyGotOne)  // Should only get one update list
    return(S_OK);
  alreadyGotOne=1;


  // Count the updates;
  tmp = updates;
  while( tmp != NULL )
  {
    tmp = tmp->next;
    numupdates++;
  }

  // Got a list of updates - If an update is required, the user must either
  //   patch or quit.

  tmp = updates;

  LogMsg("Found an update");

  // We have a required update

  char upreq[256];
  char title[128];

  LoadString(Global_instance, TXT_AN_UPGRADE_AVAILABLE, upreq, sizeof(upreq));
  strcat(upreq,"\n");
  LoadString(Global_instance, TXT_DOWNLOAD_NOW, upreq+strlen(upreq), sizeof(upreq));
  LoadString(Global_instance, TXT_UPGRADE_AVAILABLE, title, sizeof(title));

  if( MessageBox(g_PrimaryWindow,upreq,title, MB_YESNO ) == IDNO )
  {
    // If they don't want to patch now, just exit...
    //DBGMSG("Must patch to continue, so exit");
    exit(0);
  }

  // Do the downloads
  while( tmp != NULL )
  {
    g_Update = tmp;

    char dloading[256];
  	 LoadString(Global_instance, TXT_DOWNLOADING_FILE, dloading, sizeof(dloading));
    sprintf( g_UpdateString, dloading, ++i, numupdates );

    LogMsg("Creating Download dialog box");
    //if( (retval=DialogBox(Global_instance, MAKEINTRESOURCE(IDD_DOWNLOAD_DIALOG), g_PrimaryWindow,
    //    (DLGPROC)Download_Dialog_Proc)) != 1 )

    retval=DialogBox(Global_instance, MAKEINTRESOURCE(IDD_DOWNLOAD_DIALOG), g_PrimaryWindow,(DLGPROC)Download_Dialog_Proc);

    if (g_Finished != 1)
    {
      // Download failed
      //DBGMSG("Download failed: "<<retval);
      SetEvent(Events[ABORT_EVENT]);
      return(E_FAIL);
    }
    tmp = tmp->next;
  }
  // Quit so the launcher can apply the patches
  exit(0);
  return(S_OK);  // make silly compiler happy
}






STDMETHODIMP CChatEventSink::OnServerError(HRESULT, LPCSTR)
{
  LogMsg("Server Error");
  return(S_OK);
}

STDMETHODIMP CChatEventSink::OnMessageOfTheDay(HRESULT, LPCSTR)
{
  return(S_OK);
}



STDMETHODIMP CChatEventSink::OnPrivateAction(HRESULT, User *,  LPCSTR)
{
  return(S_OK);
}


STDMETHODIMP CChatEventSink::OnPublicAction(HRESULT, Channel *, User *,  LPCSTR)
{
  return(S_OK);
}


STDMETHODIMP CChatEventSink::OnPrivateGameOptions(HRESULT, User *,  LPCSTR)
{
  return(S_OK);
}


STDMETHODIMP CChatEventSink::OnPublicGameOptions(HRESULT, Channel *, User *,  LPCSTR)
{
  return(S_OK);
}


STDMETHODIMP CChatEventSink::OnGameStart(HRESULT, Channel *, User *,  int)
{
  return(S_OK);
}


STDMETHODIMP CChatEventSink::OnUserKick(HRESULT, Channel *, User *,  User *)
{
  return(S_OK);
}


STDMETHODIMP CChatEventSink::OnUserIP(HRESULT, User *)
{
  return(S_OK);
}

STDMETHODIMP CChatEventSink::OnSquadInfo(HRESULT, unsigned long, Squad *)
{
  return(S_OK);
}

*/
